package simpledb.tx.concurrency;

import simpledb.file.Block;
import java.util.*;

/**
 * The lock table, which provides methods to lock and unlock blocks. If a
 * transaction requests a lock that causes a conflict with an existing lock,
 * then that transaction is placed on a wait list. There is only one wait list
 * for all blocks. When the last lock on a block is unlocked, then all
 * transactions are removed from the wait list and rescheduled. If one of those
 * transactions discovers that the lock it is waiting for is still locked, it
 * will place itself back on the wait list.<br>
 * 锁定表，它提供锁定和解锁块的方法。 如果事务请求导致与现有锁冲突的锁，则该事务被放置在等待列表上。 所有块只有一个等待列表。
 * 当块上的最后一个锁被解锁时，所有事务将从等待列表中删除并重新计划。 如果其中一个事务发现它正在等待的锁仍然被锁定，它会将自己放回到等待列表。 共享锁
 * sLock共享锁，互斥锁xLock。sLock在字典中int值不断增加来表示，xLock通过在字典中int值置-1来表示。
 * 
 * @author Edward Sciore
 */
class LockTable {
  private static final long MAX_TIME = 10000; // 10 seconds

  private Map<Block, Integer> locks = new HashMap<Block, Integer>();

  /**
   * Grants an SLock on the specified block. If an XLock exists when the method
   * is called, then the calling thread will be placed on a wait list until the
   * lock is released. If the thread remains on the wait list for a certain
   * amount of time (currently 10 seconds), then an exception is thrown. <br>
   * 授予指定块上的SLock(共享锁)。 如果在调用方法时存在XLock(互斥锁)，那么调用线程将被放置在等待列表上，直到锁被释放。
   * 如果线程保持在等待列表上一定时间（当前为10秒），则抛出异常。<br>
   * 锁表提供接口，实现的是对block的加锁、解锁，粒度是底层的数据块Block。 锁表维护的是一个
   * <Block,int>的字典。在处理访问数据块的冲突的时候，实现的是一个忙等待机制：在每个Block上维护了一个等待队列，
   * 当事务请求一个数据块并加锁的时候，与一个已存在的锁有冲突，则将该事务加入等待队列。只是，所有的block共享一个等待队列，同时，
   * 字段中block对应的int值记录了该block上等待的事务的个数。
   * 
   * 当blockc上最后一个锁释放后，等待队列中所有相关的事务同时释放，重新调度。之后，没有请求成功的事务，重新入队。
   * 
   * @param blk a reference to the disk block
   */
  synchronized void sLock(Block blk) {
    try {
      long timestamp = System.currentTimeMillis();
      while (hasXlock(blk) && !waitingTooLong(timestamp))
        wait(MAX_TIME);
      if (hasXlock(blk))
        throw new LockAbortException();
      int val = getLockVal(blk); // will not be negative
      locks.put(blk, val + 1);
    } catch (InterruptedException e) {
      throw new LockAbortException();
    }
  }

  /**
   * Grants an XLock on the specified block. If a lock of any type exists when
   * the method is called, then the calling thread will be placed on a wait list
   * until the locks are released. If the thread remains on the wait list for a
   * certain amount of time (currently 10 seconds), then an exception is thrown.
   * <br>
   * 授予指定块上的XLock(互斥锁)。 如果在调用方法时存在任何类型的锁，那么调用线程将被放置在等待列表上，直到锁被释放。
   * 如果线程保持在等待列表上一定时间（当前为10秒），则抛出异常。
   * 
   * @param blk a reference to the disk block 磁盘块的引用
   */
  synchronized void xLock(Block blk) {
    try {
      long timestamp = System.currentTimeMillis();
      while (hasOtherSLocks(blk) && !waitingTooLong(timestamp))
        wait(MAX_TIME);
      if (hasOtherSLocks(blk))
        throw new LockAbortException();
      locks.put(blk, -1);
    } catch (InterruptedException e) {
      throw new LockAbortException();
    }
  }

  /**
   * Releases a lock on the specified block. If this lock is the last lock on
   * that block, then the waiting transactions are notified. <br>
   * 释放指定块的锁，如果该锁是这个块的最后一个锁，那么会通知等待的事务
   * 
   * @param blk a reference to the disk block
   */
  synchronized void unlock(Block blk) {
    int val = getLockVal(blk);
    if (val > 1)// sLock
      locks.put(blk, val - 1);
    else {// xLock
      locks.remove(blk);
      notifyAll();
    }
  }

  /**
   * 当前块是否存在等待的事务
   * 
   * @param blk
   * @return
   * @author zhikang.peng
   * @date 2016年12月5日上午10:25:04
   * @since 1.0
   */
  private boolean hasXlock(Block blk) {
    return getLockVal(blk) < 0;
  }

  /**
   * 当前块是否存在等待的事务
   * 
   * @param blk
   * @return
   * @author zhikang.peng
   * @date 2016年12月5日上午10:25:40
   * @since 1.0
   */
  private boolean hasOtherSLocks(Block blk) {
    return getLockVal(blk) > 1;
  }

  private boolean waitingTooLong(long starttime) {
    return System.currentTimeMillis() - starttime > MAX_TIME;
  }

  /**
   * 获取当前块等待的事务个数
   * 
   * @param blk
   * @return
   * @author zhikang.peng
   * @date 2016年12月5日上午10:25:59
   * @since 1.0
   */
  private int getLockVal(Block blk) {
    Integer ival = locks.get(blk);
    return (ival == null) ? 0 : ival.intValue();
  }
}
